<template>
  <div v-bind="$attrs" class="ttt-step" :style="stepStyle">
    <div v-if="isMinus"
         class="ttt-step-minus"
         :class="{disabled: (stepValue <= stepMin || disabled), radius}"
         @click="handleClick('minus')">
      <i class="iconfont icon-minus" />
    </div>
    <input v-model="stepValue"
           type="number"
           inputmode="decimal"
           :min="stepMin"
           :max="stepMax"
           :step="step"
           :readonly="readonly"
           :disabled="disabled"
           @input="handleInput"
           @blur="handleBlur" />
    <div v-if="isAdd"
         class="ttt-step-add"
         :class="{disabled: (stepValue >= stepMax || disabled), radius}"
         @click="handleClick('add')">
      <i class="iconfont icon-add" />
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, ref, watch, watchEffect } from 'vue'
import Big from 'big.js'
import type { PropType, CSSProperties } from 'vue'

const emits = defineEmits<{
  (e: 'update:modelValue', val: number): void
  (e: 'change', val: number): void
  (e: 'minus', val: number): void
  (e: 'add', val: number): void
}>()

const props = defineProps({
  // 双向绑定
  modelValue: {
    type: Number as PropType<number>,
    default: 0,
    required: true
  },
  // 间距
  gap: {
    type: Number as PropType<number>,
    default: 10
  },
  // 步距
  step: {
    type: [String, Number] as PropType<string | number>,
    default: 1
  },
  // 强制步距 比如 1不是0.3步距的整数倍 开启 forcedStep后 将最大值自动改为最接近最大的整数倍最大值
  forcedStep: {
    type: Boolean as PropType<boolean>,
    default: false
  },
  // 最小值
  min: {
    type: [String, Number] as PropType<string | number>,
    default: -Infinity
  },
  // 最大值
  max: {
    type: [String, Number] as PropType<string | number>,
    default: Infinity
  },
  // 圆角
  radius: {
    type: Boolean as PropType<boolean>,
    default: false
  },
  // 是否减号
  isMinus: {
    type: Boolean as PropType<boolean>,
    default: true
  },
  // 是否加号
  isAdd: {
    type: Boolean as PropType<boolean>,
    default: true
  },
  // readonly
  readonly: {
    type: Boolean as PropType<boolean>,
    default: false
  },
  // disabled
  disabled: {
    type: Boolean as PropType<boolean>,
    default: false
  }
})

const stepValue = ref<number>(props.modelValue)
const stepMin = ref<number>(-Infinity)
const stepMax = ref<number>(Infinity)
const stepStyle = computed<CSSProperties>((): CSSProperties => {
  return {
    gap: `${props.gap}rem`
  }
})

const handleClick = (type: 'minus' | 'add') => {
  if (props.disabled) return
  const path = type === 'minus' ? -1 : 1
  const step = Number.isNaN(Number(props.step)) ? 1 : Number(props.step)
  if (stepValue.value % step !== 0) {
    if (stepValue.value < 0) {
      stepValue.value = Number(new Big(parseInt(new Big(stepValue.value).div(step).toFixed().toString())).times(step))
    } else {
      stepValue.value = Number(new Big(Math.floor(Number(new Big(stepValue.value).div(step).toFixed()))).times(step))
    }
  }
  stepValue.value = new Big(stepValue.value).plus(new Big(step).times(path)).toNumber()
  if (stepValue.value <= stepMin.value) stepValue.value = stepMin.value
  if (stepValue.value >= stepMax.value) stepValue.value = stepMax.value
  emits('update:modelValue', stepValue.value)
  emits('change', stepValue.value)
  type === 'minus' ? emits('minus', stepValue.value) : emits('add', stepValue.value)
}

const handleInput = () => {
  if (stepValue.value <= stepMin.value && stepMin.value !== 0) stepValue.value = stepMin.value
  if (stepValue.value >= stepMax.value) stepValue.value = stepMax.value
  emits('update:modelValue', stepValue.value)
  emits('change', stepValue.value)
}

const handleBlur = () => {
  const step = Number.isNaN(Number(props.step)) ? 1 : Number(props.step)
  const isStep = stepValue.value % step === 0
  if (stepValue.value <= stepMin.value) stepValue.value = stepMin.value
  if (stepValue.value >= stepMax.value) stepValue.value = stepMax.value
  if (!isStep) {
    if (stepValue.value < 0) {
      stepValue.value = Number(new Big(parseInt(new Big(stepValue.value).div(step).toFixed().toString())).times(step))
    } else {
      stepValue.value = Number(new Big(Math.floor(Number(new Big(stepValue.value).div(step).toFixed()))).times(step))
    }
  }
  emits('update:modelValue', stepValue.value)
  emits('change', stepValue.value)
}

watch(() => props.modelValue, (val: number) => {
  stepValue.value = val
})

watchEffect(() => {
  const min = Number.isNaN(Number(props.min)) ? -Infinity : Number(props.min)
  const max = Number.isNaN(Number(props.max)) ? Infinity : Number(props.max)
  if (props.forcedStep) {
    const step = Number.isNaN(Number(props.step)) ? 1 : Number(props.step)
    stepMin.value = min === -Infinity ? -Infinity : Number(new Big(parseInt(new Big(min).div(step).toFixed().toString())).times(step))
    stepMax.value = max === Infinity ? Infinity : Number(new Big(Math.floor(Number(new Big(max).div(step).toFixed()))).times(step))
  } else {
    stepMin.value = min
    stepMax.value = max
  }
})
</script>

<style scoped lang="scss">
.ttt-step {
  width: 300rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--step-height);

  .ttt-step-minus, .ttt-step-add {
    min-width: var(--step-height);
    width: var(--step-height);
    height: var(--step-height);
    font-size: 40rem;
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    color: var(--theme-border-color);
    cursor: pointer;
    border-radius: var(--base-rudias);
    border: 1rem solid var(--theme-border-color);

    &.disabled {
      border-color: var(--theme-border-disabled-color);
      color: var(--theme-border-disabled-color);
    }

    &.radius {
      border-radius: 50%;
    }
  }

  > input {
    text-align: center;
    width: 100%;
    height: var(--step-height);
    box-sizing: border-box;
    border-radius: var(--base-rudias);
    border: 1rem solid var(--theme-border-color);
    outline: none;

    &:disabled {
      border-color: var(--theme-border-disabled-color);
      color: var(--theme-border-disabled-color);
    }
  }
}
</style>
